import http from 'http';
import https from 'https';
import Static from './static';
import colors from './colors';
import Proxy from './proxy';
import { RouteType } from './route';

type HttpHandler = (req: Request & http.IncomingMessage, res: Response & http.ServerResponse, next: Function)=> void;

type RouteTableItem = {
    location: string
    hanlder: HttpHandler
}

type RoutePlugin= (request: any, response: any, next: Function) => void;

type RouterType = {
    path?: string
    children?: RouteType[]
    type: string
    plugins?: any[]
}



const matchLocation = function(location: string, url: string) {
    const locations = location.split("/");
    const hashs = url.split("/");
    const props: any = { location: url };
    if(locations.length==hashs.length){
        var results = locations.filter(function(ele,pos){
            var _hash = hashs[pos];
            if(_hash.indexOf("?")!=-1){
                    var _hashs = _hash.split("?");
                    hashs[pos] = _hashs[0];
                    var eles = _hashs[1].split("&");
                    for(var i=0;i<eles.length;i++){
                        var objs = eles[i].split("=");
                        props[objs[0]] = objs[1];
                    }
            }
            if(ele.indexOf(":")!=-1){
                props[ele.split(":")[1]] = hashs[pos];
                return true;
            } else if(/\{(\w+)\}/.test(ele)) {
                const matches: RegExpMatchArray | null = ele.match(/\{(\w+)\}/)
                if(matches) props[matches[1]] = hashs[pos]
                return true
            } else {
                return ele == hashs[pos];
            }
        })
        return results.length == locations.length ? props : null;
    }
    return null;
}
const matchRegexp = function(location: string, url: string) {
    const reg = new RegExp(location, 'gmi');
    const params = [];
    let match: any = {};
    reg.lastIndex = 0;
    while(match = reg.exec(url)) {
        params.push(match);
    }
    if(params.length===0) return null;
    else return [...params];
}
function parseUrlByRouteTable(table: any[] = [], url: string) : {
    value: any,
    args: any[]
    flag?: any
}[] {
    // 匹配普通路由
    let routes = table.filter((route: any)=>{
        if(!route.location){
            console.info(colors.red, '[similar-server][WARNNING]route.location is undefined');
            return null;
        }
        let flag = matchLocation(route.location, url);
        return flag;
    }).map((route: any)=> {
        return {
            value: route.hanlder,
            args: [matchLocation(route.location, url)]
        }
    })
    // 普通路由未匹配上，则开启正则路由匹配
    // 原则上按照匹配到数量，递增排序
    if(routes.length===0) {
        routes = table.filter(function (route: any) {
            let flag = matchRegexp(route.location, url);
            route.flag = flag
            return flag;
        }).map((route: any)=> {
            var flag = matchRegexp(route.location, url)
            return {
                value: route.hanlder,
                args: [flag],
                flag: flag
            }
        })
        routes.sort((a: any, b: any)=> a.flag.length - b.flag.length)
    }

    return routes;
}

export class Application {
    routeTable: RouteTableItem[] = []
    routePlugins: RoutePlugin[] = []
    _router: RouterType = {
        children: [],
        plugins: [],
        type: 'router'
    }
    inited = false

    get https() {
        const app = this
        return {
            credentials: null,
            listen: function() {
                const server = https.createServer(this.credentials as any, (req, res) => app.handle(req, res));
                return server.listen.apply(server, arguments as any);
            }
        }
    }

    get http() {
        const app = this
        return {
            listen: function() {
                const server = http.createServer((req, res) => app.handle(req, res));
                return server.listen.apply(server, arguments as any);
            }
        }
    }

    /**
     * 匹配路由和插件
     * @param url 路由路径
     * @returns 
     */
    match(url: string) {
        const handlers: any[] = parseUrlByRouteTable(this.routeTable, url);
        // 优先解析插件确定的路由
        // 其次解析普通路由规则
        // 最后解析正则路由规则
        return this.routePlugins.map((route: any)=> {
            return {
                value: route,
                args: []
            }
        }).concat(handlers)
    }

    /**
     * http 请求入口
     */
    handle<
        Request extends http.IncomingMessage = http.IncomingMessage,
        Response extends http.ServerResponse = http.ServerResponse,
    >(req: Request, res: Response, callback?: Function) {
        // 匹配一批出来，组合成 next stack
        const stack = this.match(req.url || '');
        let idx = 0;
        res.appendHeader("Server", "SimilarServer")
        function next() {
            const done = callback || function(req: any, res: any) {
                res.setHeader('Content-Type', 'text/html');
                res.writeHead(404, {'Content-Type': 'text/plain'});
                res.end('ok');
            }
            var layer = stack[idx++]
            if (layer == undefined) {
                done(req, res);
            }
            var layerValue = layer.value;
            var layerArgs = layer.args || [];

            if(typeof layerValue == 'function') {
              layerValue(req, res, next, ...layerArgs);
            }
            if(typeof layerValue == 'object') {
              layerValue.handle(req, res, next, ...layerArgs);
            }
        }
        // 调用第一个匹配项
        next();
    }

    /**
     * 定义路由
     */
    router(router: any) {
        this._router = router || this._router;
    }

    /**
     * 添加插件 同use方法
     */
    plugin(_plugin: RoutePlugin) {
        this.routePlugins.push(_plugin);
    }

    /**
     * 定义静态文件目录
     */
    static(dir: string) {
        this.routePlugins.push(Static(dir));
    }

    // 添加路由
    route(location: string, hanlder: HttpHandler) {
        this.routeTable.push({
            location, 
            hanlder
        });
    }

    /**
     * 添加插件 同plugin方法
     * @param _plugin 插件
     */
    use(_plugin: RoutePlugin) {
        this.routePlugins.push(_plugin);
    }

    proxy(location: string, options: any) {
        if(typeof options !== 'object') throw new Error('param options must be an object');
        if(typeof options.target  !== 'string') throw new Error('options.target must be a proxy server\'s url');
        this.routeTable.push({location, hanlder: Proxy(options)});
    }

    /**
     * 初始化路由和插件
     */
    init() {
        if(this.inited) {
            return
        }
        this.inited = false
        // 解析路由
        this.routePlugins = this.routePlugins.concat(this._router.plugins || []);
        const createRoute = (location: string, hanlder: HttpHandler) => {
            this.routeTable.push({ location, hanlder });
        }
        function createRoutesByChildren(children: RouteType[] = [], root: RouteType & RouterType) {
            if(['router', 'route'].indexOf(root.type)===-1) return;
            const routes = [];
            if(!Array.isArray(children)) children = [children];
            children.forEach((child: any, index) => {
                // 由Controller自定义方法构建，构建自身Controller的同时，需要构建Controller自定义方法
                if(Array.isArray(child.children) && child.path===undefined) {
                    createRoutesByChildren(child.children, root);
                    root.location && createRoute(root.location, child);
                    return;
                }
                // 普通router和route方法构建
                if(Array.isArray(child.children)) {
                    if(root.type=='router') child.location = '/'+child.path;
                    else child.location = root.location+'/'+child.path;
                    createRoutesByChildren(child.children, child);
                } else {
                    root.location && createRoute(root.location, child);
                }
            });
        }
        const createRoutes = function(root: RouterType) {
            createRoutesByChildren(root.children ?? [],root);
        }
        
        createRoutes(this._router);

        this.routeTable.forEach((route: any)=>{
            console.info(colors.yellow, '[similar-server][ROUTE]['+route.location+']', route.hanlder);
        })
        this.routePlugins.forEach((plugin: RoutePlugin)=>{
            console.info(colors.yellow,  '[similar-server][PLUGINGS]['+plugin.name+']');
        })
        this.inited = true
    }

    listen() {
        if(!this.inited) this.init()
        const server = http.createServer((req, res) => this.handle(req, res));
        return server.listen.apply(server, arguments as any);
    }
}

export class ApplicationFactory {
    static create() {
        return new Application()
    }
}